// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 包列表实现``go list''命令。
package list

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sort"
	"strings"
	"text/template"

	"cmd/go/internal/base"
	"cmd/go/internal/cache"
	"cmd/go/internal/cfg"
	"cmd/go/internal/load"
	"cmd/go/internal/modinfo"
	"cmd/go/internal/modload"
	"cmd/go/internal/str"
	"cmd/go/internal/work"
)

var CmdList = &base.Command{
	// 注意：-f-json-m是显式列出的，因为它们是最常见的列表标志。
	// 不要发送删除它们的CLs，因为它们被[list flags]覆盖。
	UsageLine: "go list [-f format] [-json] [-m] [list flags] [build flags] [packages]",
	Short:     "list packages or modules",
	Long: `
List lists the named packages, one per line.
The most commonly-used flags are -f and -json, which control the form
of the output printed for each package. Other list flags, documented below,
control more specific details.

The default output shows the package import path:

    bytes
    encoding/json
    github.com/gorilla/mux
    golang.org/x/net/html

The -f flag specifies an alternate format for the list, using the
syntax of package template. The default output is equivalent
to -f '{{.ImportPath}}'. The struct being passed to the template is:

    type Package struct {
        Dir           string   // 包含包源的目录
        ImportPath    string   // 目录中包的导入路径
        ImportComment string   // 包语句的导入注释中的路径
        Name          string   // 包名
        Doc           string   // 包文档字符串
        Target        string   // 安装路径
        Shlib         string   // 包含此包的共享库（仅在-linkshared时设置）
        Goroot        bool     // 这个包在根目录中吗？
        Standard      bool     // 这个包是标准Go库的一部分吗？
        Stale         bool     // “go install”对这个软件包有什么作用吗？
        StaleReason   string   // 对Stale==true的解释
        Root          string   // 包含此包的根目录或路径目录
        ConflictDir   string   // 此目录在$GOPATH中隐藏目录
        BinaryOnly    bool     // 仅二进制软件包（不再支持）
        ForTest       string   // 软件包仅用于命名测试
        Export        string   // 包含导出数据的文件（使用-export时）
        BuildID       string   // 编译包的生成ID（使用-export时）
        Module        *Module  // 关于包的包含模块的信息（如果有）（可以为零）
        Match         []string // 与此包匹配的命令行模式
        DepOnly       bool     // 包只是一个依赖项，没有显式列出

        // 源文件
        GoFiles         []string   // .go源文件（不包括cgofile、TestGoFiles、XTestGoFiles）
        CgoFiles        []string   // .go导入“C”的源文件
        CompiledGoFiles []string   // .go文件提交给编译器（使用-compiled时）
        IgnoredGoFiles  []string   // .go由于生成约束而忽略源文件
        IgnoredOtherFiles []string // 由于生成约束而忽略非.go源文件
        CFiles          []string   // .c源文件
        CXXFiles        []string   // .cc、.cxx和.cpp源文件
        MFiles          []string   // .m源文件
        HFiles          []string   // .h、.hh、.hpp和.hxx源文件
        FFiles          []string   // .f、.f、.for和.f90 Fortran源文件
        SFiles          []string   // .s源文件
        SwigFiles       []string   // .swig文件
        SwigCXXFiles    []string   // .swigcxx文件
        SysoFiles       []string   // .syso要添加到存档的对象文件
        TestGoFiles     []string   // _包中的test.go文件
        XTestGoFiles    []string   // _包外的test.go文件

        // 嵌入文件
        EmbedPatterns      []string // None
        EmbedFiles         []string // 由嵌入模式匹配的文件
        TestEmbedPatterns  []string // None
        TestEmbedFiles     []string // 由TestEmbedPatterns匹配的文件
        XTestEmbedPatterns []string // None
        XTestEmbedFiles    []string // 由XTestEmbedPatterns匹配的文件

        // Cgo指令
        CgoCFLAGS    []string // cgo:C编译器的标志
        CgoCPPFLAGS  []string // cgo:C预处理器的标志
        CgoCXXFLAGS  []string // CGO：C++编译器的标志
        CgoFFLAGS    []string // cgo:Fortran编译器的标志
        CgoLDFLAGS   []string // cgo:链接器的标志
        CgoPkgConfig []string // cgo:pkg配置名称

        // 依赖信息
        Imports      []string          // 导入此包使用的路径
        ImportMap    map[string]string // 从源导入映射到导入路径（省略标识条目）
        Deps         []string          // 所有（递归）导入的依赖项
        TestImports  []string          // 从TestGoFiles导入
        XTestImports []string          // 从XTestGoFiles导入

        // 错误信息
        Incomplete bool            // 此包或依赖项有错误
        Error      *PackageError   // 加载包时出错
        DepsErrors []*PackageError // 加载依赖项时出错
    }

Packages stored in vendor directories report an ImportPath that includes the
path to the vendor directory (for example, "d/vendor/p" instead of "p"),
so that the ImportPath uniquely identifies a given copy of a package.
The Imports, Deps, TestImports, and XTestImports lists also contain these
expanded import paths. See golang.org/s/go15vendor for more about vendoring.

The error information, if any, is

    type PackageError struct {
        ImportStack   []string // 从命令行上命名的包到此包的最短路径
        Pos           string   // 错误位置（如果存在，文件：行：列）
        Err           string   // 错误本身
    }

The module information is a Module struct, defined in the discussion
of list -m below.

The template function "join" calls strings.Join.

The template function "context" returns the build context, defined as:

    type Context struct {
        GOARCH        string   // 目标体系结构
        GOOS          string   // 目标操作系统
        GOROOT        string   // 扎根
        GOPATH        string   // 走小路
        CgoEnabled    bool     // 是否可以使用cgo
        UseAllFiles   bool     // 使用文件而不考虑+生成行、文件名
        Compiler      string   // 编译器在计算目标路径时要假定
        BuildTags     []string // 生成约束以匹配in+生成线
        ToolTags      []string // 特定于工具链的构建约束
        ReleaseTags   []string // 版本当前版本与兼容
        InstallSuffix string   // 要在安装目录名称中使用的后缀
    }

For more information about the meaning of these fields see the documentation
for the go/build package's Context type.

The -json flag causes the package data to be printed in JSON format
instead of using the template format.

The -compiled flag causes list to set CompiledGoFiles to the Go source
files presented to the compiler. Typically this means that it repeats
the files listed in GoFiles and then also adds the Go code generated
by processing CgoFiles and SwigFiles. The Imports list contains the
union of all imports from both GoFiles and CompiledGoFiles.

The -deps flag causes list to iterate over not just the named packages
but also all their dependencies. It visits them in a depth-first post-order
traversal, so that a package is listed only after all its dependencies.
Packages not explicitly listed on the command line will have the DepOnly
field set to true.

The -e flag changes the handling of erroneous packages, those that
cannot be found or are malformed. By default, the list command
prints an error to standard error for each erroneous package and
omits the packages from consideration during the usual printing.
With the -e flag, the list command never prints errors to standard
error and instead processes the erroneous packages with the usual
printing. Erroneous packages will have a non-empty ImportPath and
a non-nil Error field; other information may or may not be missing
(zeroed).

The -export flag causes list to set the Export field to the name of a
file containing up-to-date export information for the given package.

The -find flag causes list to identify the named packages but not
resolve their dependencies: the Imports and Deps lists will be empty.

The -test flag causes list to report not only the named packages
but also their test binaries (for packages with tests), to convey to
source code analysis tools exactly how test binaries are constructed.
The reported import path for a test binary is the import path of
the package followed by a ".test" suffix, as in "math/rand.test".
When building a test, it is sometimes necessary to rebuild certain
dependencies specially for that test (most commonly the tested
package itself). The reported import path of a package recompiled
for a particular test binary is followed by a space and the name of
the test binary in brackets, as in "math/rand [math/rand.test]"
or "regexp [sort.test]". The ForTest field is also set to the name
of the package being tested ("math/rand" or "sort" in the previous
examples).

The Dir, Target, Shlib, Root, ConflictDir, and Export file paths
are all absolute paths.

By default, the lists GoFiles, CgoFiles, and so on hold names of files in Dir
(that is, paths relative to Dir, not absolute paths).
The generated files added when using the -compiled and -test flags
are absolute paths referring to cached copies of generated Go source files.
Although they are Go source files, the paths may not end in ".go".

The -m flag causes list to list modules instead of packages.

When listing modules, the -f flag still specifies a format template
applied to a Go struct, but now a Module struct:

    type Module struct {
        Path      string       // 模块路径
        Version   string       // 模块版本
        Versions  []string     // 可用模块版本（带-版本）
        Replace   *Module      // 替换为此模块
        Time      *time.Time   // 创建版本的时间
        Update    *Module      // 可用更新（如果有）（使用-u）
        Main      bool         // 这是主模块吗？
        Indirect  bool         // 此模块是否仅是主模块的间接依赖项？
        Dir       string       // 保存此模块文件的目录（如果有）
        GoMod     string       // 加载此模块时使用的go.mod文件的路径（如果有）
        GoVersion string       // 模块中使用的go版本
        Retracted string       // 收回信息，如有（带-retracted或-u）
        Error     *ModuleError // 加载模块时出错
    }

    type ModuleError struct {
        Err string // 错误本身
    }

The file GoMod refers to may be outside the module directory if the
module is in the module cache or if the -modfile flag is used.

The default output is to print the module path and then
information about the version and replacement if any.
For example, 'go list -m all' might print:

    my/main/module
    golang.org/x/text v0.3.0 => /tmp/text
    rsc.io/pdf v0.1.1

The Module struct has a String method that formats this
line of output, so that the default format is equivalent
to -f '{{.String}}'.

Note that when a module has been replaced, its Replace field
describes the replacement module, and its Dir field is set to
the replacement's source code, if present. (That is, if Replace
is non-nil, then Dir is set to Replace.Dir, with no access to
the replaced source code.)

The -u flag adds information about available upgrades.
When the latest version of a given module is newer than
the current one, list -u sets the Module's Update field
to information about the newer module. list -u will also set
the module's Retracted field if the current version is retracted.
The Module's String method indicates an available upgrade by
formatting the newer version in brackets after the current version.
If a version is retracted, the string "(retracted)" will follow it.
For example, 'go list -m -u all' might print:

    my/main/module
    golang.org/x/text v0.3.0 [v0.4.0] => /tmp/text
    rsc.io/pdf v0.1.1 (retracted) [v0.1.2]

(For tools, 'go list -m -u -json all' may be more convenient to parse.)

The -versions flag causes list to set the Module's Versions field
to a list of all known versions of that module, ordered according
to semantic versioning, earliest to latest. The flag also changes
the default output format to display the module path followed by the
space-separated version list.

The -retracted flag causes list to report information about retracted
module versions. When -retracted is used with -f or -json, the Retracted
field will be set to a string explaining why the version was retracted.
The string is taken from comments on the retract directive in the
module's go.mod file. When -retracted is used with -versions, retracted
versions are listed together with unretracted versions. The -retracted
flag may be used with or without -m.

The arguments to list -m are interpreted as a list of modules, not packages.
The main module is the module containing the current directory.
The active modules are the main module and its dependencies.
With no arguments, list -m shows the main module.
With arguments, list -m shows the modules specified by the arguments.
Any of the active modules can be specified by its module path.
The special pattern "all" specifies all the active modules, first the main
module and then dependencies sorted by module path.
A pattern containing "..." specifies the active modules whose
module paths match the pattern.
A query of the form path@version specifies the result of that query,
which is not limited to active modules.
See 'go help modules' for more about module queries.

The template function "module" takes a single string argument
that must be a module path or query and returns the specified
module as a Module struct. If an error occurs, the result will
be a Module struct with a non-nil Error field.

For more about build flags, see 'go help build'.

For more about specifying packages, see 'go help packages'.

For more about modules, see https:// golang.org/ref/mod。
	`,
}

func init() {
	CmdList.Run = runList // 中断初始循环
	work.AddBuildFlags(CmdList, work.DefaultBuildFlags)
}

var (
	listCompiled  = CmdList.Flag.Bool("compiled", false, "")
	listDeps      = CmdList.Flag.Bool("deps", false, "")
	listE         = CmdList.Flag.Bool("e", false, "")
	listExport    = CmdList.Flag.Bool("export", false, "")
	listFmt       = CmdList.Flag.String("f", "", "")
	listFind      = CmdList.Flag.Bool("find", false, "")
	listJson      = CmdList.Flag.Bool("json", false, "")
	listM         = CmdList.Flag.Bool("m", false, "")
	listRetracted = CmdList.Flag.Bool("retracted", false, "")
	listTest      = CmdList.Flag.Bool("test", false, "")
	listU         = CmdList.Flag.Bool("u", false, "")
	listVersions  = CmdList.Flag.Bool("versions", false, "")
)

var nl = []byte{'\n'}

func runList(ctx context.Context, cmd *base.Command, args []string) {
	if *listFmt != "" && *listJson == true {
		base.Fatalf("go list -f cannot be used with -json")
	}

	work.BuildInit()
	out := newTrackingWriter(os.Stdout)
	defer out.w.Flush()

	if *listFmt == "" {
		if *listM {
			*listFmt = "{{.String}}"
			if *listVersions {
				*listFmt = `{{.Path}}{{range .Versions}} {{.}}{{end}}{{if .Deprecated}} (deprecated){{end}}`
			}
		} else {
			*listFmt = "{{.ImportPath}}"
		}
	}

	var do func(interface{})
	if *listJson {
		do = func(x interface{}) {
			b, err := json.MarshalIndent(x, "", "\t")
			if err != nil {
				out.Flush()
				base.Fatalf("%s", err)
			}
			out.Write(b)
			out.Write(nl)
		}
	} else {
		var cachedCtxt *Context
		context := func() *Context {
			if cachedCtxt == nil {
				cachedCtxt = newContext(&cfg.BuildContext)
			}
			return cachedCtxt
		}
		fm := template.FuncMap{
			"join":    strings.Join,
			"context": context,
			"module":  func(path string) *modinfo.ModulePublic { return modload.ModuleInfo(ctx, path) },
		}
		tmpl, err := template.New("main").Funcs(fm).Parse(*listFmt)
		if err != nil {
			base.Fatalf("%s", err)
		}
		do = func(x interface{}) {
			if err := tmpl.Execute(out, x); err != nil {
				out.Flush()
				base.Fatalf("%s", err)
			}
			if out.NeedNL() {
				out.Write(nl)
			}
		}
	}

	modload.Init()
	if *listRetracted {
		if cfg.BuildMod == "vendor" {
			base.Fatalf("go list -retracted cannot be used when vendoring is enabled")
		}
		if !modload.Enabled() {
			base.Fatalf("go list -retracted can only be used in module-aware mode")
		}
	}

	if *listM {
		// 模块模式。
		if *listCompiled {
			base.Fatalf("go list -compiled cannot be used with -m")
		}
		if *listDeps {
			// TODO（rsc）：这可能意味着-m。
			base.Fatalf("go list -deps cannot be used with -m")
		}
		if *listExport {
			base.Fatalf("go list -export cannot be used with -m")
		}
		if *listFind {
			base.Fatalf("go list -find cannot be used with -m")
		}
		if *listTest {
			base.Fatalf("go list -test cannot be used with -m")
		}

		if modload.Init(); !modload.Enabled() {
			base.Fatalf("go list -m: not using modules")
		}

		modload.LoadModFile(ctx) // 将cfg.BuildMod设置为副作用。
		if cfg.BuildMod == "vendor" {
			const actionDisabledFormat = "go list -m: can't %s using the vendor directory\n\t(Use -mod=mod or -mod=readonly to bypass.)"

			if *listVersions {
				base.Fatalf(actionDisabledFormat, "determine available versions")
			}
			if *listU {
				base.Fatalf(actionDisabledFormat, "determine available upgrades")
			}

			for _, arg := range args {
				// 在供应商模式下，模块图不完整：它只包含
				// 显式模块依赖项和在中提供包的模块
				// 导入图形。拒绝包含更多信息的查询。
				if arg == "all" {
					base.Fatalf(actionDisabledFormat, "compute 'all'")
				}
				if strings.Contains(arg, "...") {
					base.Fatalf(actionDisabledFormat, "match module patterns")
				}
			}
		}

		var mode modload.ListMode
		if *listU {
			mode |= modload.ListU | modload.ListRetracted | modload.ListDeprecated
		}
		if *listRetracted {
			mode |= modload.ListRetracted
		}
		if *listVersions {
			mode |= modload.ListVersions
			if *listRetracted {
				mode |= modload.ListRetractedVersions
			}
		}
		mods, err := modload.ListModules(ctx, args, mode)
		if !*listE {
			for _, m := range mods {
				if m.Error != nil {
					base.Errorf("go list -m: %v", m.Error.Err)
				}
			}
			if err != nil {
				base.Errorf("go list -m: %v", err)
			}
			base.ExitIfErrors()
		}
		for _, m := range mods {
			do(m)
		}
		return
	}

	// 包模式（非-m）。
	if *listU {
		base.Fatalf("go list -u can only be used with -m")
	}
	if *listVersions {
		base.Fatalf("go list -versions can only be used with -m")
	}

	// 这些配对毫无意义。
	if *listFind && *listDeps {
		base.Fatalf("go list -deps cannot be used with -find")
	}
	if *listFind && *listTest {
		base.Fatalf("go list -test cannot be used with -find")
	}

	pkgOpts := load.PackageOpts{
		IgnoreImports:   *listFind,
		ModResolveTests: *listTest,
	}
	pkgs := load.PackagesAndErrors(ctx, pkgOpts, args)
	if !*listE {
		w := 0
		for _, pkg := range pkgs {
			if pkg.Error != nil {
				base.Errorf("%v", pkg.Error)
				continue
			}
			pkgs[w] = pkg
			w++
		}
		pkgs = pkgs[:w]
		base.ExitIfErrors()
	}

	if cache.Default() == nil {
		// 这些标志返回指向生成缓存的文件名，
		// 因此，构建缓存必须存在。
		if *listCompiled {
			base.Fatalf("go list -compiled requires build cache")
		}
		if *listExport {
			base.Fatalf("go list -export requires build cache")
		}
		if *listTest {
			base.Fatalf("go list -test requires build cache")
		}
	}

	if *listTest {
		c := cache.Default()
		// 将测试二进制文件添加到要列出的包中。
		for _, p := range pkgs {
			if len(p.TestGoFiles)+len(p.XTestGoFiles) > 0 {
				var pmain, ptest, pxtest *load.Package
				var err error
				if *listE {
					pmain, ptest, pxtest = load.TestPackagesAndErrors(ctx, pkgOpts, p, nil)
				} else {
					pmain, ptest, pxtest, err = load.TestPackagesFor(ctx, pkgOpts, p, nil)
					if err != nil {
						base.Errorf("can't load test package: %s", err)
					}
				}
				if pmain != nil {
					pkgs = append(pkgs, pmain)
					data := *pmain.Internal.TestmainGo
					h := cache.NewHash("testmain")
					h.Write([]byte("testmain\n"))
					h.Write(data)
					out, _, err := c.Put(h.Sum(), bytes.NewReader(data))
					if err != nil {
						base.Fatalf("%s", err)
					}
					pmain.GoFiles[0] = c.OutputFile(out)
				}
				if ptest != nil && ptest != p {
					pkgs = append(pkgs, ptest)
				}
				if pxtest != nil {
					pkgs = append(pkgs, pxtest)
				}
			}
		}
	}

	// 记住哪些包在命令行中命名。
	cmdline := make(map[*load.Package]bool)
	for _, p := range pkgs {
		cmdline[p] = true
	}

	if *listDeps {
		// 注意：这会更改所列包的顺序
		// 从“在命令行上写入”到
		// “深度优先后序遍历”。
		// （给定节点的依赖项探索顺序
		// 按字母顺序排列，与.Deps中列出的相同。）
		// 注意，-deps在测试后应用，
		// 因此，您只能获得命名对象的测试描述
		// 显式地在命令行上，而不是针对所有依赖项。
		pkgs = loadPackageList(pkgs)
	}

	// 我们需要运行构建来收集信息吗？
	needStale := *listJson || strings.Contains(*listFmt, ".Stale")
	if needStale || *listExport || *listCompiled {
		var b work.Builder
		b.Init()
		b.IsCmdList = true
		b.NeedExport = *listExport
		b.NeedCompiledGoFiles = *listCompiled
		a := &work.Action{}
		// TODO:使用pkgsFilter？
		for _, p := range pkgs {
			if len(p.GoFiles)+len(p.CgoFiles) > 0 {
				a.Deps = append(a.Deps, b.AutoAction(work.ModeInstall, work.ModeInstall, p))
			}
		}
		b.Do(ctx, a)
	}

	for _, p := range pkgs {
		// 在列表中显示供应商扩展的路径
		p.TestImports = p.Resolve(p.TestImports)
		p.XTestImports = p.Resolve(p.XTestImports)
		p.DepOnly = !cmdline[p]

		if *listCompiled {
			p.Imports = str.StringList(p.Imports, p.Internal.CompiledImports)
		}
	}

	if *listTest {
		all := pkgs
		if !*listDeps {
			all = loadPackageList(pkgs)
		}
		// 更新导入路径以区分真正的包p
		// 从p重新编译用于q测试。
		// 这只能在生成代码完成后发生
		// 查看导入路径，因为它会变得非常混乱
		// 如果它看到这些。
		old := make(map[string]string)
		for _, p := range all {
			if p.ForTest != "" {
				new := p.Desc()
				old[new] = p.ImportPath
				p.ImportPath = new
			}
			p.DepOnly = !cmdline[p]
		}
		// 更新导入路径列表以使用新字符串。
		m := make(map[string]string)
		for _, p := range all {
			for _, p1 := range p.Internal.Imports {
				if p1.ForTest != "" {
					m[old[p1.ImportPath]] = p1.ImportPath
				}
			}
			for i, old := range p.Imports {
				if new := m[old]; new != "" {
					p.Imports[i] = new
				}
			}
			for old := range m {
				delete(m, old)
			}
		}
		// 使用新字符串重新计算deps列表，从左起。
		for _, p := range all {
			deps := make(map[string]bool)
			for _, p1 := range p.Internal.Imports {
				deps[p1.ImportPath] = true
				for _, d := range p1.Deps {
					deps[d] = true
				}
			}
			p.Deps = make([]string, 0, len(deps))
			for d := range deps {
				p.Deps = append(p.Deps, d)
			}
			sort.Strings(p.Deps)
		}
	}

	// TODO（golang.org/issue/40676）：该机制可以扩展以支持
	// -u没有-m。
	if *listRetracted {
		// 提供将要打印的包的模块的装载收回。
		// TODO（golang.org/issue/40775）：来自同一模块的包引用
		// 不同的ModulePublic实例。如果他们都能指出这一点就好了
		// 同样的例子。这将需要更多的全球国家参与
		// modload.loaded，因此应该首先对其进行重构。现在，我们更新
		// 所有实例。
		modToArg := make(map[*modinfo.ModulePublic]string)
		argToMods := make(map[string][]*modinfo.ModulePublic)
		var args []string
		addModule := func(mod *modinfo.ModulePublic) {
			if mod.Version == "" {
				return
			}
			arg := fmt.Sprintf("%s@%s", mod.Path, mod.Version)
			if argToMods[arg] == nil {
				args = append(args, arg)
			}
			argToMods[arg] = append(argToMods[arg], mod)
			modToArg[mod] = arg
		}
		for _, p := range pkgs {
			if p.Module == nil {
				continue
			}
			addModule(p.Module)
			if p.Module.Replace != nil {
				addModule(p.Module.Replace)
			}
		}

		if len(args) > 0 {
			var mode modload.ListMode
			if *listRetracted {
				mode |= modload.ListRetracted
			}
			rmods, err := modload.ListModules(ctx, args, mode)
			if err != nil && !*listE {
				base.Errorf("go list -retracted: %v", err)
			}
			for i, arg := range args {
				rmod := rmods[i]
				for _, mod := range argToMods[arg] {
					mod.Retracted = rmod.Retracted
					if rmod.Error != nil && mod.Error == nil {
						mod.Error = rmod.Error
					}
				}
			}
		}
	}

	// 在p.ImportMap中记录非标识导入映射。
	for _, p := range pkgs {
		nRaw := len(p.Internal.RawImports)
		for i, path := range p.Imports {
			var srcPath string
			if i < nRaw {
				srcPath = p.Internal.RawImports[i]
			} else {
				// 此路径不在原始导入中，因此它必须是导入
				// 只能在Edgofiles中找到。这些路径可以在中找到
				// 编译导入。
				srcPath = p.Internal.CompiledImports[i-nRaw]
			}

			if path != srcPath {
				if p.ImportMap == nil {
					p.ImportMap = make(map[string]string)
				}
				p.ImportMap[srcPath] = path
			}
		}
	}

	for _, p := range pkgs {
		do(&p.PackagePublic)
	}
}

// loadPackageList与load.PackageList类似，但会打印错误消息并退出
// 如果未设置listE且展开列表中的任何包，则状态为非零
// 有错误。
func loadPackageList(roots []*load.Package) []*load.Package {
	pkgs := load.PackageList(roots)

	if !*listE {
		for _, pkg := range pkgs {
			if pkg.Error != nil {
				base.Errorf("%v", pkg.Error)
			}
		}
	}

	return pkgs
}

// TrackingWriter跟踪每次写入时写入的最后一个字节
// 我们可以避免打印一个换行符，如果一个换行符已经被写入或删除
// 如果根本没有输出。
type TrackingWriter struct {
	w    *bufio.Writer
	last byte
}

func newTrackingWriter(w io.Writer) *TrackingWriter {
	return &TrackingWriter{
		w:    bufio.NewWriter(w),
		last: '\n',
	}
}

func (t *TrackingWriter) Write(p []byte) (n int, err error) {
	n, err = t.w.Write(p)
	if n > 0 {
		t.last = p[n-1]
	}
	return
}

func (t *TrackingWriter) Flush() {
	t.w.Flush()
}

func (t *TrackingWriter) NeedNL() bool {
	return t.last != '\n'
}
